package com.qmmessage.sdk.common.util;

import com.qmmessage.sdk.common.exception.QmMessageSdkException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;

/**
 * signature utils
 *
 * @author qm
 */
public class QmSignatureUtil {
    private static final String SEPARATOR = "&";
    private static final String BODY_PARAM_NAME = "BodyRaw";

    /**
     * generate signature
     *
     * @param timestamp     current time
     * @param uuid          uuid
     * @param queryParamMap query params
     * @param bodyStr       body string
     * @param accessSecret  secret
     * @return signature string
     * @throws QmMessageSdkException qm exception
     */
    public static String generateSignature(Long timestamp, String uuid, Map<String, String> queryParamMap, String bodyStr, String accessSecret) throws QmMessageSdkException {
        // param sort
        TreeMap<String, String> sortedParams = new TreeMap<>(queryParamMap);
        // append params
        StringBuilder stringToSign = new StringBuilder();
        stringToSign.insert(0, timestamp.toString() + SEPARATOR + uuid + SEPARATOR);
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            stringToSign.append(entry.getKey()).append("=").append(entry.getValue()).append(SEPARATOR);
        }
        // add body
        if (QmStrUtils.isNotBlank(bodyStr)) {
            stringToSign.append(BODY_PARAM_NAME + "=").append(Base64.getEncoder().encodeToString(bodyStr.getBytes(StandardCharsets.UTF_8)));
        }
        // remove last &
        if (stringToSign.length() > 0 && stringToSign.toString().endsWith(SEPARATOR)) {
            stringToSign.setLength(stringToSign.length() - SEPARATOR.length());
        }
        byte[] hmac256 = hmac256(accessSecret, stringToSign.toString());
        String signature = DatatypeConverter.printHexBinary(hmac256).toLowerCase();
        return Base64.getEncoder().encodeToString(signature.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * hmac256
     *
     * @param accessSecret secret
     * @param stringToSign sign string
     * @return byte data
     * @throws QmMessageSdkException qm exception
     */
    private static byte[] hmac256(String accessSecret, String stringToSign) throws QmMessageSdkException {
        Mac mac;
        try {
            mac = Mac.getInstance("HmacSHA256");
        } catch (NoSuchAlgorithmException e) {
            throw new QmMessageSdkException("HmacSHA256 is not supported.");
        }
        SecretKeySpec secretKeySpec = new SecretKeySpec(accessSecret.getBytes(StandardCharsets.UTF_8), mac.getAlgorithm());
        try {
            mac.init(secretKeySpec);
        } catch (InvalidKeyException e) {
            throw new QmMessageSdkException("InvalidKeyException");
        }
        return mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
    }
}
